/*
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/pwm.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/clk.h>
#include <linux/errno.h>
#include <mach/debug.h>

#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_panel.h>

#include "../lombo/lombo_doss.h"
#include "../lombo/lombo_vo_manage.h"

#define WIDTH	(720)
#define HEIGHT	(576)
#define HBP		(69)
#define HFP		(12)
#define HSW		(63)
#define HACT		(WIDTH)
#define VBP_F1	(19)
#define VFP_F1	(2)
#define VSW_F1	(3)
#define VACT_F1	(HEIGHT / 2)
#define VT_F1	(VBP_F1 + VFP_F1 + VSW_F1 + VACT_F1)
#define VBP_F2	(19)
#define VFP_F2	(2)
#define VSW_F2	(3)
#define VACT_F2	(HEIGHT / 2)
#define VT_F2	(VBP_F2 + VFP_F2 + VSW_F2 + VACT_F2 + 1)
#define DCLK	(13500000)

#define DCS_MDELAY_FLAG 0

struct panel_tp2803cvbs_desc {
	const struct drm_display_mode *modes;
	unsigned int num_modes;
	const struct display_timing *timings;
	unsigned int num_timings;

	unsigned int bpc;

	struct {
		unsigned int width;
		unsigned int height;
	} size;

	/**
	 * @prepare: the time (in milliseconds) that it takes for the panel to
	 *           become ready and start receiving video data
	 * @enable: the time (in milliseconds) that it takes for the panel to
	 *          display the first valid frame after starting to receive
	 *          video data
	 * @disable: the time (in milliseconds) that it takes for the panel to
	 *           turn the display off (no content is visible)
	 * @unprepare: the time (in milliseconds) that it takes for the panel
	 *             to power itself down completely
	 */
	struct {
		unsigned int prepare;
		unsigned int enable;
		unsigned int disable;
		unsigned int unprepare;
	} delay;

	lombo_vo_dev_if_t interface;
	u32 cmd_align;
	u32 bus_format;
	unsigned long flag;
	enum lombo_vo_pixel_format format;
};

struct viss_md {
	struct clk	*srcclk0;
	struct clk	*srcclk1;
	struct clk	*viss_reset;
	struct clk	*viss_gate;
	struct clk	*viss_axi_gate;
	void __iomem	*regs;
};

struct panel_tp2803cvbs {
	struct drm_panel base;
	bool prepared;
	bool enabled;

	struct device *dev;
	const struct panel_tp2803cvbs_desc *desc;

	struct backlight_device *backlight;
	struct regulator *supply;

	struct viss_md *vmd;
	struct pinctrl *pctrl;

	int reset_gpio;
};

static struct i2c_client *panel_i2c_client[3];

static u32 tp2803cvbs_init_code[] = {
	3, 0x8a, 0x02, 0x1a,  /*0x1a, 0x5a(test-pat) is ok */
	3, 0x8a, 0x03, 0x63,
	3, 0x8a, 0x05, 0x20,
	3, 0x8a, 0x07, 0x01,
	3, 0x8a, 0x08, 0x20,
	3, 0x8a, 0x09, 0x42,
	3, 0x8a, 0x0a, 0x28,
	3, 0x8a, 0x0b, 0x05,
	3, 0x8a, 0x0c, 0x04,
	3, 0x8a, 0x0d, 0x04,
	3, 0x8a, 0x0e, 0x3c,
	3, 0x8a, 0x0f, 0x60,
	3, 0x8a, 0x10, 0x03,
	3, 0x8a, 0x11, 0x20,
	3, 0x8a, 0x12, 0x04,
	3, 0x8a, 0x13, 0x3f,
	3, 0x8a, 0x15, 0x3b,
	3, 0x8a, 0x1b, 0xa0,
	3, 0x8a, 0x1c, 0x88,
	3, 0x8a, 0x1d, 0xc1,
	3, 0x8a, 0x1e, 0x5f,
	3, 0x8a, 0x20, 0x48,
	3, 0x8a, 0x21, 0xbb,
	3, 0x8a, 0x22, 0x2e,
	3, 0x8a, 0x23, 0x8b,
	3, 0x8a, 0x29, 0x34,
	3, 0x8a, 0x2a, 0x19,
	3, 0x8a, 0x3b, 0xd0,
	3, 0x8a, 0x3c, 0x10,
	3, 0x8a, 0x3e, 0x00,
	3, 0x8a, 0x45, 0x45,
	3, 0x8a, 0xf1, 0x08,
	3, 0x8a, 0xf2, 0x0e,
	3, 0x8a, 0xf3, 0x4a,
	3, 0x8a, 0xf4, 0x61,
	3, 0x8a, 0x3a, 0x00,

	3, 0x8a, 0xff, 0xff,
};

static struct i2c_client *panel_get_i2c_client(const unsigned short i2c_addr)
{
	const __u32 num = ARRAY_SIZE(panel_i2c_client);
	__u32 i;

	for (i = 0; i < num; i++) {
		struct i2c_client *client = panel_i2c_client[i];

		if (client && (client->addr == i2c_addr))
			return client;
	}

	return NULL;
}

static s32 panel_i2c_write_reg_array(u32 tidx, u32 *reg, u32 len)
{
	u32 i = 0;

	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);

	while (i < len) {
		if (DCS_MDELAY_FLAG == reg[i]) {
			mdelay(reg[i + 1]);
			i += reg[i] + 2;
		} else {
			char buf[4];
			struct i2c_client *client =
				panel_get_i2c_client(reg[i + 1] >> 1);

			if (client) {
				buf[0] = reg[i + 2]; /* reg addr */
				buf[1] = reg[i + 3]; /* reg value */
				if (2 != i2c_master_send(client, buf, 2)) {
					DRM_ERROR("%d\n", __LINE__);
					return -1;
				}

				#if 0
				char read_buf[4];
				if (1 != i2c_master_send(client, &buf[0], 1)) {
					DRM_ERROR("%d\n", __LINE__);
					return -1;
				}
				read_buf[0] = 0;
				if (1 != i2c_master_recv(client, read_buf, 1)) {
					DRM_ERROR("%d\n", __LINE__);
					return -1;
				}
				pr_debug("read_buf=0x%x\n", read_buf[0]);
				#endif
			} else {
				pr_err("%d: null i2c_client @%x\n",
					__LINE__, reg[i + 1] >> 1);
			}
			i += reg[i] + 1;
		}
	}
	return 0;
}

static inline struct panel_tp2803cvbs *to_panel_tp2803cvbs
				(struct drm_panel *panel)
{
	return container_of(panel, struct panel_tp2803cvbs, base);
}

static int panel_tp2803cvbs_disable(struct drm_panel *panel)
{
	struct panel_tp2803cvbs *p = to_panel_tp2803cvbs(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (!p->enabled)
		return 0;

	if (p->backlight) {
		p->backlight->props.power = FB_BLANK_POWERDOWN;
		backlight_update_status(p->backlight);
	}

	if (p->desc && p->desc->delay.disable)
		msleep(p->desc->delay.disable);

	p->enabled = false;

	return 0;
}

static int panel_tp2803cvbs_unprepare(struct drm_panel *panel)
{
	struct panel_tp2803cvbs *p = to_panel_tp2803cvbs(panel);

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (!p->prepared)
		return 0;

	if (p->reset_gpio)
		gpio_direction_output(p->reset_gpio, 0);

	if (p->supply)
		regulator_disable(p->supply);

	if (p->desc && p->desc->delay.unprepare)
		msleep(p->desc->delay.unprepare);

	p->prepared = false;

	return 0;
}

static int panel_tp2803cvbs_prepare(struct drm_panel *panel)
{
	struct panel_tp2803cvbs *p = to_panel_tp2803cvbs(panel);
	int err;

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (p->prepared)
		return 0;
	p->prepared = true;

	if (p->supply) {
		err = regulator_enable(p->supply);
		if (err < 0) {
			dev_err(panel->dev,
				"failed to enable supply: %d\n", err);
			return err;
		}
	}

	if (p->reset_gpio) {
		gpio_direction_output(p->reset_gpio, 1);
		msleep(10);
		gpio_direction_output(p->reset_gpio, 0);
		msleep(50);
		gpio_direction_output(p->reset_gpio, 1);
		msleep(120);
	}

	if (p->desc && p->desc->delay.prepare)
		msleep(p->desc->delay.prepare);

	panel_i2c_write_reg_array(3,
				  tp2803cvbs_init_code,
				  ARRAY_SIZE(tp2803cvbs_init_code));

	return 0;
}

static int panel_tp2803cvbs_enable(struct drm_panel *panel)
{
	struct panel_tp2803cvbs *p = to_panel_tp2803cvbs(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (p->enabled)
		return 0;
	p->enabled = true;

	if (p->desc && p->desc->delay.enable)
		msleep(p->desc->delay.enable);

	if (p->backlight) {
		p->backlight->props.power = FB_BLANK_UNBLANK;
		backlight_update_status(p->backlight);
	}

	return 0;
}

static int panel_tp2803cvbs_get_fixed_modes(
	struct panel_tp2803cvbs *panel)
{
	struct drm_connector *connector = panel->base.connector;
	struct drm_device *drm = panel->base.drm;
	struct drm_display_mode *mode;
	unsigned int i, num = 0;

	if (!panel->desc)
		return 0;

	for (i = 0; i < panel->desc->num_modes; i++) {
		const struct drm_display_mode *m = panel->desc->modes + i;

		mode = drm_mode_duplicate(drm, m);
		if (!mode) {
			DRM_ERROR("failed to add mode %ux%u@%u\n",
				m->hdisplay, m->vdisplay, m->vrefresh);
			continue;
		}

		drm_mode_set_name(mode);

		drm_mode_probed_add(connector, mode);
		num++;
	}

	connector->display_info.bpc = panel->desc->bpc;
	connector->display_info.width_mm = panel->desc->size.width;
	connector->display_info.height_mm = panel->desc->size.height;

	return num;
}

static int panel_tp2803cvbs_get_modes(struct drm_panel *panel)
{
	struct panel_tp2803cvbs *p = to_panel_tp2803cvbs(panel);
	int num = 0;

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	/* add hard-coded panel modes */
	num += panel_tp2803cvbs_get_fixed_modes(p);

	return num;
}

static int panel_tp2803cvbs_get_timings(struct drm_panel *panel,
				    unsigned int num_timings,
				    struct display_timing *timings)
{
	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);
	return 0;
}

static int panel_tp2803cvbs_get_mode_private(
	struct drm_panel *panel,
	const struct drm_display_mode *mode,
	void **private)
{
	struct panel_tp2803cvbs *p = to_panel_tp2803cvbs(panel);

	if (!(p && p->desc && p->desc->modes))
		return -EINVAL;

	if (!mode) {
		*private = p->desc->modes[0].private;
		return 0;
	}

	if (drm_mode_equal_no_clocks(mode, &p->desc->modes[0])) {
		DRM_DEBUG_KMS("private=%p\n",
			p->desc->modes[0].private);
		*private = p->desc->modes[0].private;
		return 0;
	}
	return -EINVAL;
}

static int panel_i2c_probe(struct i2c_client *client,
	const struct i2c_device_id *id)
{
	__u32 i;

	if (!client) {
		DRM_ERROR("%d: null i2c_client\n", __LINE__);
		return -1;
	}

	panel_i2c_client[0] = client;
	for (i = 1; i < ARRAY_SIZE(panel_i2c_client); i++) {
		panel_i2c_client[i] = i2c_new_dummy(client->adapter,
					client->addr + i);
		if (!panel_i2c_client[i]) {
			dev_err(&client->adapter->dev,
				"address 0x%02x unavailable\n",
				client->addr + i);
			return -EADDRINUSE;
		} else {
			dev_info(&client->adapter->dev,
				"i2c_new_dummy 0x%02x 0x%02x ok\n",
				client->addr + i, panel_i2c_client[i]->addr);
		}
	}

	dev_info(&client->adapter->dev,
		"attached panel tp2803cvbs into i2c adapter successfully\n");

	return 0;
}

static int panel_i2c_remove(struct i2c_client *client)
{
	dev_info(&client->adapter->dev,
		"detached panel tp2803cvbs from i2c adapter successfully\n");

	return 0;
}

static const struct i2c_device_id panel_i2c_id[] = {
	{ "tp2803cvbs_i2c", 0 },
	{ },
};

static struct of_device_id panel_i2c_match_types[] = {
	{
		.compatible = "tp2803cvbs_i2c",
	}, {
		/* end node */
	}
};

struct i2c_driver panel_i2c_cvbs_driver = {
	.driver = {
		.name = "tp2803cvbs_i2c",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(panel_i2c_match_types),
	},
	.id_table = panel_i2c_id,
	.probe = panel_i2c_probe,
	.remove = panel_i2c_remove,
	.command = NULL,
};

static const struct drm_panel_funcs panel_tp2803cvbs_funcs = {
	.disable = panel_tp2803cvbs_disable,
	.unprepare = panel_tp2803cvbs_unprepare,
	.prepare = panel_tp2803cvbs_prepare,
	.enable = panel_tp2803cvbs_enable,
	.get_modes = panel_tp2803cvbs_get_modes,
	.get_timings = panel_tp2803cvbs_get_timings,
	.get_mode_private = panel_tp2803cvbs_get_mode_private,
};

static int panel_tp2803cvbs_probe(struct device *dev,
			       const struct panel_tp2803cvbs_desc *desc)
{
	struct device_node *np = dev->of_node;
	struct panel_tp2803cvbs *panel;
	enum of_gpio_flags flags;
	int err;
	struct lombo_vo_device *vo = to_lombo_vo_device(dev);

	dev_dbg(dev, "%s,%d\n", __FILE__, __LINE__);

	panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
	if (!panel)
		return -ENOMEM;

	if (i2c_add_driver(&panel_i2c_cvbs_driver)) {
		DRM_ERROR("failed to register panel i2c driver\n");
		return -ENOENT;
	}
	panel->reset_gpio = of_get_named_gpio_flags(
		np, "reset_gpio", 0, &flags);
	DRM_DEBUG_KMS("reset_gpio %d, flags 0x%x\n", panel->reset_gpio, flags);
	if (!gpio_is_valid(panel->reset_gpio)) {
		DRM_ERROR("reset_gpio %d invalid\n", panel->reset_gpio);
		panel->reset_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->reset_gpio, "reset_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->reset_gpio);
			panel->reset_gpio = 0;
		}
	}

	panel->enabled = false;
	panel->prepared = false;
	panel->desc = desc;
	panel->dev = dev;

	vo->interface = desc->interface;
	vo->cmd_align = desc->cmd_align;
	vo->format = desc->format;
	vo->mode_flags = desc->flag;

	drm_panel_init(&panel->base);
	panel->base.dev = dev;
	panel->base.funcs = &panel_tp2803cvbs_funcs;

	err = drm_panel_add(&panel->base);
	if (err < 0)
		goto err_out;

	lombo_vo_attach(vo);
	dev_set_drvdata(dev, panel);

	DRM_DEBUG_KMS("[%d] over.\n", __LINE__);

	return 0;

err_out:
	return err;
}

static int panel_tp2803cvbs_remove(struct lombo_vo_device *vo)
{
	struct panel_tp2803cvbs *panel = dev_get_drvdata(&vo->dev);

	lombo_vo_detach(vo);
	drm_panel_detach(&panel->base);
	drm_panel_remove(&panel->base);

	panel_tp2803cvbs_disable(&panel->base);

	if (panel->backlight)
		put_device(&panel->backlight->dev);

	return 0;
}

static void panel_tp2803cvbs_shutdown(struct lombo_vo_device *vo)
{
	struct panel_tp2803cvbs *panel = dev_get_drvdata(&vo->dev);

	panel_tp2803cvbs_disable(&panel->base);
}

static struct lombo_vo_dev_config tp2803cvbs_vo = {
	.dev_if = VO_DEV_BT1120_16BIT,
	.bt_if = {
		.field_pol = TCON_IO_POS,
		.vb_pol = TCON_IO_POS,
		.hb_pol = TCON_IO_POS,
		.field_map = TCON_FUNC_MAP_FIELD,
		.vs_map = TCON_FUNC_MAP_VSYNC,
		.hs_map = TCON_FUNC_MAP_HSYNC,
		.de_map = TCON_FUNC_MAP_DE,
		.ds_mode = TCON_BT_YC_DS_DIRECTLY,
		.yc_senquence = TCON_BT_YC_SEQ_Y0CB0Y1CR0,
		.is_clk_ddr = false,
	},

	/* timing, reference to tcon_timings_t */
	.timing.is_interlace = true,
	.timing.field1.vt = VT_F1,
	.timing.field1.vact = VACT_F1,
	.timing.field1.vfp = VFP_F1,
	.timing.field1.vsw = VSW_F1,
	.timing.field2.vt = VT_F2,
	.timing.field2.vact = VACT_F2,
	.timing.field2.vfp = VFP_F2,
	.timing.field2.vfp_float = 1,
	.timing.field2.vsw = VSW_F2,
	.timing.hline.ht = HSW + HBP + HACT + HFP,
	.timing.hline.hact = HACT,
	.timing.hline.hfp = HFP,
	.timing.hline.hsw = HSW,
	.timing.dclk_freq = DCLK,
	/* resolution in pixel */
	.timing.width = WIDTH,
	.timing.height = HEIGHT,

	/* format, reference to tcon_output_fmt_t */
	.format = TCON_FMT_YUV422,
	/* frame rate control (dither) enable */
	.frc_en = false,
	.csc_en = true,
	.has_te = false,
	.te_pol = TCON_IO_POS,
};

static const struct drm_display_mode tp2803cvbs_mode = {
	/* dclk_freq */
	.clock = DCLK / 1000,
	/* width */
	.hdisplay = WIDTH,
	/* hsync_start = hdisplay + hfp */
	.hsync_start = WIDTH + HFP,
	/* hsync_end = hdisplay + hfp + hsw */
	.hsync_end = WIDTH + HFP + HSW,
	/* htotal = hdisplay + hfp + hsw + hbp */
	.htotal = WIDTH + HFP + HSW + HBP,
	/* height */
	.vdisplay = HEIGHT,
	/* vsync_start = vdisplay + vfp */
	.vsync_start = HEIGHT + VFP_F1,
	/* vsync_end = vdisplay + vfp + vsw */
	.vsync_end = HEIGHT + VFP_F1 + VSW_F1,
	/* vtotal = vdisplay + vfp + vsw + vbp */
	.vtotal = HEIGHT + VFP_F1 + VSW_F1 + VBP_F1,
	.vrefresh = 25,

	.private_size = sizeof(tp2803cvbs_vo),
	.private = (int *)&tp2803cvbs_vo,
};

static const struct panel_tp2803cvbs_desc tp2803cvbs_desc = {
	.modes = &tp2803cvbs_mode,
	.num_modes = 1,
	.bpc = 6,
	.size = {
		.width = 69,
		.height = 123,
	},
};

static const struct of_device_id panel_tp2803cvbs_of_match[] = {
	{
		.compatible = "panel,tp2803cvbs",
		.data = &tp2803cvbs_desc,
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(of, panel_tp2803cvbs_of_match);

static int panel_tp2803cvbs_pre_probe(struct lombo_vo_device *vo)
{
	const struct of_device_id *id;
	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);

	if (!of_device_is_available(vo->dev.of_node))
		return -ENODEV;

	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);
	id = of_match_node(panel_tp2803cvbs_of_match, vo->dev.of_node);
	if (!id)
		return -ENODEV;

	return panel_tp2803cvbs_probe(&vo->dev, id->data);
}

static struct lombo_vo_driver panel_tp2803cvbs_driver = {
	.driver = {
		.name = "panel-tp2803cvbs",
		.of_match_table = panel_tp2803cvbs_of_match,
	},
	.probe = panel_tp2803cvbs_pre_probe,
	.remove = panel_tp2803cvbs_remove,
	.shutdown = panel_tp2803cvbs_shutdown,
};

static int __init panel_tp2803cvbs_init(void)
{
	int err;

	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);
	err = lombo_vo_driver_register(&panel_tp2803cvbs_driver);
	if (err < 0)
		return err;

	return 0;
}
module_init(panel_tp2803cvbs_init);

static void __exit panel_tp2803cvbs_exit(void)
{
	lombo_vo_driver_unregister(&panel_tp2803cvbs_driver);
}
module_exit(panel_tp2803cvbs_exit);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("DRM Driver for Panels tp2803cvbs");
MODULE_LICENSE("GPL and additional rights");
